﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MacomberMapSystem.Common.Serialization;
using System.Data.Common;
using System.Xml;
using MacomberMapSystem.Common.CIM;
using System.Drawing;
using System.Reflection;
using System.Windows.Forms;
using MacomberMapSystem.Common.Internals;
using System.IO;
using MacomberMapSystem.Common.Processing;
using MacomberMapSystem.Common.User_Interfaces.One_Lines;
using MacomberMapSystem.Common.Exports;
using MacomberMapSystem.Common.User_Interfaces;
using System.Drawing.Imaging;

namespace MacomberMapSystem.Common.Database
{
    /// <summary>
    /// This class holds information on a one-line in MM
    /// </summary>
    public class MM_Database_OneLine : MM_Serializable, IComparable<MM_Database_OneLine>
    {
        #region Variable declarations
        /// <summary>The names that this one-line could refer to</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.FieldValue)]
        public String[] Names;

        /// <summary>The models for which this item is identified</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.FieldValue)]
        public MM_Database_Model[] Models;

        /// <summary>The XML configuration for the one-line</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.FieldValue)]
        public XmlDocument Document;

        /// <summary>The TEID of the one-line</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.FieldValue)]
        public UInt64 TEID;

        /// <summary>The rdf:ID of the one-line</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.FieldValue)]
        public CIM_RdfID rdfID;

        /// <summary>The layout revision version, that tracks the version of the one-line</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.FieldValue)]
        public int LayoutVersion = 0;


        /// <summary>
        /// The one-line enumerator type
        /// </summary>
        public enum OneLineTypeEnum
        {
            /// <summary>This one-line is of a substation</summary>
            Substation = 0,

            /// <summary>This one-line is a breaker-to-breaker</summary>
            BreakerToBreaker = 1,

            /// <summary>This one-line is based on operatorship</summary>
            Operatorship = 2,

            /// <summary>Blackstart components</summary>
            Blackstart = 3,

            /// <summary>A special one-line type</summary>
            Special = 4
        }

        /// <summary>The special one-line routine to be performed</summary>
        public SpecialOneLineDelegate SpecialOneLineRoutine;

        /// <summary>The type of one-line this is</summary>
        public OneLineTypeEnum OneLineType;

        /// <summary>Our delegate for special one-line generation</summary>
        /// <param name="CurModel"></param>
        /// <param name="TargetPath"></param>
        public delegate string[] SpecialOneLineDelegate(MM_Database_Model CurModel, String TargetFolder);
        #endregion

        /// <summary>
        /// Initialize a one-line that's a special routine
        /// </summary>
        /// <param name="SpecialOneLineRoutine"></param>
        public MM_Database_OneLine(SpecialOneLineDelegate SpecialOneLineRoutine)
            : base(null, null)
        {
            this.OneLineType = OneLineTypeEnum.Special;
            this.SpecialOneLineRoutine = SpecialOneLineRoutine;
        }

        /// <summary>
        /// Initialize a new one-line from XML
        /// </summary>
        /// <param name="xDoc"></param>
        /// <param name="rdfID"></param>
        /// <param name="TEID"></param>
        /// <param name="Name"></param>
        /// <param name="Model"></param>
        public MM_Database_OneLine(XmlDocument xDoc, CIM_RdfID rdfID, UInt64 TEID, String Name, MM_Database_Model Model)
            : base(null, null)
        {
            this.Document = xDoc;
            this.Models = new MM_Database_Model[] { Model };
            this.Names = new string[] { Name };
            this.OneLineType = OneLineTypeEnum.Blackstart;
            this.rdfID = rdfID;
            this.TEID = TEID;
        }

        /// <summary>
        /// Initialize a new one-line
        /// </summary>
        /// <param name="dRd"></param>
        /// <param name="Repository">The data repository</param>
        public MM_Database_OneLine(DbDataReader dRd, MM_Repository Repository)
            : base(dRd, null, Repository)
        { }

        /// <summary>
        /// Create a blank one-line
        /// </summary>
        public MM_Database_OneLine():base(null,null)
        {
        }

        /// <summary>
        /// Create a clone of an existing one-line
        /// </summary>
        /// <param name="OneLine"></param>
        public MM_Database_OneLine(MM_Database_OneLine OneLine)
            : base(null, null)
        {
            if (OneLine == null)
                return;
            foreach (FieldInfo fI in OneLine.GetType().GetFields())
                if (fI.FieldType == typeof(XmlDocument))
                {
                    XmlDocument NewDoc = new XmlDocument();
                    NewDoc.LoadXml((fI.GetValue(OneLine) as XmlDocument).OuterXml);
                    fI.SetValue(this, NewDoc);
                }
                else
                    fI.SetValue(this, fI.GetValue(OneLine));
        }

        /// <summary>
        /// Report the one-line's name(s)
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return String.Join("/", Names);
        }


        /// <summary>
        /// Provide a mechanism for sorting the one-lines
        /// </summary>
        /// <param name="other"></param>
        /// <returns></returns>
        public int CompareTo(MM_Database_OneLine other)
        {
            //First, compare rdf:IDs
            int rdfComp = rdfID.CompareTo(other.rdfID);
            if (rdfComp != 0)
                return rdfComp;

            long ThisRecent = long.MinValue, OtherRecent = long.MinValue;
            foreach (MM_Database_Model Model in this.Models)
                ThisRecent = Math.Max(Model.ValidStart.ToBinary(), ThisRecent);
            foreach (MM_Database_Model Model in other.Models)
                OtherRecent = Math.Max(Model.ValidStart.ToBinary(), OtherRecent);
            return ThisRecent.CompareTo(OtherRecent);
        }



        /// <summary>
        /// Propigate a dropdown menu with information
        /// </summary>
        /// <param name="ts"></param>
        public void PropigateMenu(ToolStripItemCollection ts)
        {
            ts.Clear();
            ts.Add("rdf:ID: " + this.rdfID.ToString());
            ts.Add("TEID: " + this.TEID.ToString("#,##0"));
            ts.Add("Names:").Enabled = false;
            foreach (String name in Names)
                ts.Add("  " + name).Enabled = false;
            ts.Add("-");
            ts.Add("Models:").Enabled = false;
            MM_Database_Model.AddModelMenus(ts, Models, null);
        }

        /// <summary>
        /// Write out our one-line to a target path
        /// </summary>
        /// <param name="TargetPath"></param>
        /// <param name="Model"></param>
        public void WriteOneLine(String TargetPath, MM_Database_Model Model)
        {
            MM_Database_OneLine.WriteOneLine(Document, TargetPath, Model);
        }

        /// <summary>
        /// Write our one-line out to a file
        /// </summary>
        /// <param name="xDoc"></param>
        /// <param name="TargetPath"></param>
        /// <param name="Model"></param>
        public static void WriteOneLine(XmlDocument xDoc, String TargetPath, MM_Database_Model Model)
        {
            //Make sure our title is there
            if (!xDoc.DocumentElement.HasAttribute("BaseElement.Name"))
                xDoc.DocumentElement.Attributes.Append(xDoc.CreateAttribute("BaseElement.Name")).Value = xDoc.DocumentElement.Attributes["BaseElement.DisplayName"].Value;

            foreach (String strType in "Elements,Nodes".Split(','))
                foreach (XmlElement xElem in xDoc.DocumentElement[strType].ChildNodes)
                    try
                    {
                        //Locate our element
                        CIM_Element FoundElem = null;
                        if (xElem.HasAttribute("rdfID"))
                            FoundElem = Model.FromRdfID(xElem.Attributes["rdfID"].Value);
                        if (FoundElem == null && xElem.HasAttribute("BaseElement.TEID"))
                            FoundElem = Model.FromTEID(XmlConvert.ToUInt64(xElem.Attributes["BaseElement.TEID"].Value));


                        xElem.Attributes.Append(xDoc.CreateAttribute("BaseElement.Name")).Value = FoundElem.LineName;
                        xElem.Attributes.Append(xDoc.CreateAttribute("BaseElement.TEID")).Value = FoundElem.TEID;
                        CIM_Element Owner = FoundElem.FindOwner();
                        if (Owner != null)
                            xElem.Attributes.Append(xDoc.CreateAttribute("BaseElement.Owner")).Value = Owner.TEID.ToString();

                        CIM_Element Operator = FoundElem.FindOperator();
                        if (Operator != null)
                            xElem.Attributes.Append(xDoc.CreateAttribute("BaseElement.Operator")).Value = Operator.TEID.ToString();

                        CIM_Element Substation = FoundElem.Substation;
                        if (Substation != null)
                        {
                            xElem.Attributes.Append(xDoc.CreateAttribute("BaseElement.Substation")).Value = Substation.TEID;
                            xElem.Attributes.Append(xDoc.CreateAttribute("BaseElement.SubName")).Value = MM_Type_Converter.TitleCase(Substation.Name);
                            xElem.Attributes.Append(xDoc.CreateAttribute("BaseElement.SubLongName")).Value = MM_Type_Converter.TitleCase(Substation.LongName);
                        }



                        StringBuilder sB = new StringBuilder(), sB2 = new StringBuilder();
                        CIM_Trace[] Traces;
                        if (Model.TraceLinkages.TryGetValue(FoundElem.rdfID, out Traces))
                            foreach (CIM_Trace OtherTrace in Traces)
                                sB.Append((sB.Length == 0 ? "" : ",") + OtherTrace.Name);
                        xElem.Attributes.Append(xDoc.CreateAttribute("Contingencies")).Value = sB.ToString();

                        foreach (CIM_Element Elem2 in FoundElem.SPSorRAPs)
                            sB2.Append((sB2.Length == 0 ? "" : ",") + Elem2.TEID);
                        if (sB2.Length > 0)
                            xElem.Attributes.Append(xDoc.CreateAttribute("RASs")).Value = sB2.ToString();


                        //If a breaker, check for a synchroscope
                        if (FoundElem.ElemType == "Breaker" && Operator != null)
                        {
                            xElem.Attributes.Append(xDoc.CreateAttribute("BaseElement.HasSynchroscope")).Value = (Model.Db.Synchroscopes.ContainsKey(FoundElem.rdfID) || Model.Db.Synchroscopes.ContainsKey(FoundElem.Substation.rdfID) || Model.Db.Synchroscopes.ContainsKey(Operator.rdfID)).ToString();
                            xElem.Attributes.Append(xDoc.CreateAttribute("BaseElement.HasSynchrocheckRelay")).Value = (Model.Db.Synchrocheck_Relays.ContainsKey(FoundElem.rdfID) || Model.Db.Synchrocheck_Relays.ContainsKey(FoundElem.Substation.rdfID) || Model.Db.Synchrocheck_Relays.ContainsKey(Operator.rdfID)).ToString();
                        }
                        xElem.Attributes.Append(xDoc.CreateAttribute("PUNElement")).Value = Model.PUNElements.ContainsKey(FoundElem.rdfID).ToString();

                        //If a line, determine whether ZBR
                        if (FoundElem.ElemType == "Line")
                        {
                            xElem.Attributes.Append(xDoc.CreateAttribute("IsZBR")).Value = (String.IsNullOrEmpty(FoundElem["cim:Conductor.x"]) || FoundElem["cim:Conductor.x"] == "0") && (String.IsNullOrEmpty(FoundElem["cim:Conductor.r"]) || FoundElem["cim:Conductor.r"] == "0") ? "true" : "false";
                            xElem.Attributes.Append(xDoc.CreateAttribute("BaseElement.Substation1")).Value = xElem.Attributes.Append(xDoc.CreateAttribute("BaseElement.FromSubstation")).Value = FoundElem.ACSegmentStations[0].TEID;
                            xElem.Attributes.Append(xDoc.CreateAttribute("BaseElement.Substation2")).Value = xElem.Attributes.Append(xDoc.CreateAttribute("BaseElement.ToSubstation")).Value = FoundElem.ACSegmentStations[1].TEID;
                            xElem.Attributes.Append(xDoc.CreateAttribute("BaseElement.ConnectedStations")).Value = FoundElem.ACSegmentStations[0].TEID + "," + FoundElem.ACSegmentStations[1].TEID;
                            xElem.Attributes.Append(xDoc.CreateAttribute("BaseElement.Sub1Name")).Value = FoundElem.ACSegmentStations[0].Name;
                            xElem.Attributes.Append(xDoc.CreateAttribute("BaseElement.Sub2Name")).Value = FoundElem.ACSegmentStations[1].Name;
                        }
                        else //if (!xElem.HasAttribute("BaseElement.SubName"))
                            xElem.Attributes.Append(xDoc.CreateAttribute("BaseElement.SubName")).Value = FoundElem.Substation.Name;

                        //If a node, make sure our targets are updated
                        if (FoundElem.ElemType == "Node")
                            foreach (XmlElement xNodePointer in xElem.ChildNodes)
                                if (xNodePointer.HasAttribute("rdfID") && !xNodePointer.Name.EndsWith("Descriptor"))
                                    xNodePointer.Attributes.Append(xDoc.CreateAttribute("TEID")).Value = Model.FromRdfID(xNodePointer.Attributes["rdfID"].Value).TEID;


                        //If a transformer, update the windings
                        if (FoundElem.ElemType == "Transformer")
                        {
                            //Find our primary winding                                               
                            foreach (CIM_Element Winding in FoundElem.Parse("cim:TransformerWinding", null))
                                if (Winding.Attributes["cim:TransformerWinding.windingType"].Contains("primary"))
                                    xElem.Attributes.Append(xElem.OwnerDocument.CreateAttribute("BaseElement.TransformerName")).Value = Winding.Name;
                            foreach (XmlElement xWinding in xElem.SelectNodes("Winding"))
                            {
                                CIM_Element FoundWinding = null;
                                if (xWinding.HasAttribute("rdfID"))
                                    FoundWinding = Model.FromRdfID(xWinding.Attributes["rdfID"].Value);
                                if (FoundWinding == null && xWinding.HasAttribute("BaseElement.TEID"))
                                    FoundWinding = Model.FromTEID(XmlConvert.ToUInt64(xWinding.Attributes["BaseElement.TEID"].Value));
                                CIM_Element FoundNode = FoundWinding.ParseSingle("cim:Terminal>cim:ConnectivityNode", Model);
                                xWinding.Attributes.Append(xDoc.CreateAttribute("BaseElement.AssociatedNodeName")).Value = FoundNode.Name;
                                xWinding.Attributes.Append(xDoc.CreateAttribute("BaseElement.AssociatedNode")).Value = FoundNode.TEID;
                                xWinding.Attributes.Append(xDoc.CreateAttribute("BaseElement.KVLevel")).Value = FoundNode.VoltageLevel.Name;

                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine("Error: " + ex.ToString());
                    }
            using (XmlTextWriter xW = new XmlTextWriter(TargetPath, new UTF8Encoding(false)))
            {
                xW.Formatting = Formatting.Indented;
                xDoc.WriteContentTo(xW);
            }
        }

        /// <summary>
        /// Write all one-lines to a target directory
        /// </summary>
        /// <param name="Directory"></param>
        /// <param name="Repository"></param>
        /// <param name="Model"></param>
        public static void WriteOneLines(String Directory, MM_Database_Model Model, MM_Repository Repository)
        {

            using (StreamWriter sW = new StreamWriter(Path.Combine(Directory, "MM_OneLine_Export.log")))
            {

                //Write out our substation one-lines
                foreach (CIM_Element Sub in Model.Substations.Values)
                    using (DbCommand dCmd = Repository.Db.CreateCommand("SELECT DOCUMENT FROM MM_DATABASE_ONELINE WHERE UPPER(RDFID)=:0 AND MODELS LIKE :1", Sub.rdfID.ToString().ToUpper(), "%," + Model.ID.ToString() + ",%"))
                    using (DbDataReader dRd = dCmd.ExecuteReader())
                        if (dRd.Read())
                            try
                            {
                                DateTime StartTime = DateTime.Now;
                                XmlDocument xDoc = new XmlDocument();
                                xDoc.LoadXml(((string)dRd[0]).TrimStart((Char)191));
                                MM_Database_OneLine.WriteOneLine(xDoc, Path.Combine(Directory, Sub.Name + ".MM_OneLine"), Model);
                                Application.DoEvents();
                                sW.WriteLine("Success writing " + Sub.Name + ": " + (DateTime.Now - StartTime).ToString());
                            }
                            catch (Exception ex)
                            {
                                sW.WriteLine("Error writing " + Sub.Name + ": " + ex.ToString());
                            }


                //Write out our traces
                foreach (CIM_Trace Trace in Model.Traces.Values)
                    try
                    {
                        DateTime StartTime = DateTime.Now;
                        XmlDocument xDoc = CIM_BreakerToBreaker.AutoGenerateOneLine(Trace);
                        clsTomSawyer.RunLayout(xDoc, Model);
                        MM_Database_OneLine.WriteOneLine(xDoc, Path.Combine(Directory, "__" + Trace.Name + ".MM_OneLine"), Model);
                        sW.WriteLine("Success writing _" + Trace.Name + ": " + (DateTime.Now - StartTime).ToString());
                    }
                    catch (Exception ex)
                    {
                        sW.WriteLine("Error writing  __" + Trace.Name + ": " + ex.ToString());
                    }
            }
        }

        /// <summary>
        /// Write the collection of one-line images
        /// </summary>
        /// <param name="DirectoryName"></param>
        /// <param name="CurModel"></param>
        /// <param name="Repository"></param>
        /// <param name="SendConfirmation"></param>
        public static void WriteOneLineImages(String DirectoryName, MM_Database_Model CurModel, MM_Repository Repository, bool SendConfirmation)
        {
            foreach (CIM_Element SubElem in CurModel.ElementsOfType("cim:Substation"))
                if (bool.Parse(SubElem["etx:Substation.forNetwork"]) && bool.Parse(SubElem["etx:Substation.forScada"]))
                    using (DbCommand dCmd = Repository.Db.CreateCommand("SELECT ID FROM MM_DATABASE_ONELINE WHERE ONELINETYPE=:0 AND MODELS LIKE :1 AND UPPER(RDFID)=:2", 0, "%," + CurModel.ID + ",%", SubElem.rdfID.ToString().ToUpper()))
                    using (DbDataReader dRd = dCmd.ExecuteReader())
                        if (dRd.Read())
                        {
                            MM_Database_OneLine OL = Repository.Db.LoadOneLine(Convert.ToInt32(dRd["ID"]), CurModel);
                            Bitmap OutImage = MM_Image_Exporter.OneLineImage(OL, CurModel, CurModel.LocateExport(OL), Repository.Db.GetLastAssignment(OL));
                            OutImage.Save(Path.Combine(DirectoryName, SubElem.Name + ".png"), ImageFormat.Png);
                            //OneLinePreview.AssignOneLine(OL, OutImage, CurModel, Repository);
                            Application.DoEvents();
                            using (DbCommand dCmd2 = Repository.Db.CreateCommand("UPDATE MM_DATABASE_ONELINE SET IMAGE=:0 WHERE ID=:1", OutImage, dRd["ID"]))
                                dCmd2.ExecuteNonQuery();
                            OutImage.Dispose();
                        }

            //Send out our confirmation email
            if (SendConfirmation)
            {
                XmlElement xExport = Repository.xConfig.SelectSingleNode("Configuration/OneLineExport") as XmlElement;
                StringBuilder OutMessage = new StringBuilder();
                OutMessage.AppendLine(String.Format("This is the Macomber Map Loader 3's One-line image generation module, version {0}, running on {1}, by {2}\\{3} at {4}", Application.ProductVersion, Environment.MachineName, Environment.UserDomainName, Environment.UserName, DateTime.Now));
                OutMessage.AppendLine();
                OutMessage.AppendLine(String.Format("The one-line images were produced against CIM file {0}, and the results have been saved into {1}.", CurModel.Name, DirectoryName));
                MM_Email_Sending.SendEmail(xExport.Attributes["SMTPServer"].Value, xExport.Attributes["FromEmail"].Value, xExport.Attributes["FromName"].Value, xExport.Attributes["Subject"].Value, new MM_String_Collection(xExport.Attributes["MailRecipients"].Value.Split(',')), new MM_String_Collection(xExport.Attributes["CarbonCopies"].Value.Split(',')), new MM_String_Collection(xExport.Attributes["BlindCarbonCopies"].Value.Split(',')), OutMessage.ToString());
            }
        }
    }
}